<?php
/**
 * BaseTestCase.php
 *
 * Description
 *
 * @author Milkmeowo <milkmeowo@gmail.com>
 */

namespace Tests;

use GuzzleHttp\Client;
use Lukasoppermann\Httpstatus\Httpstatuscodes;
use Psr\Http\Message\ResponseInterface;
use PHPUnit\Framework\Assert as PHPUnit;

class BaseTestCase extends TestCase implements Httpstatuscodes
{

    /**
     * @var
     */
    protected $response;

    /**
     * @var
     */
    protected $guzzleResponse;

    /**
     * @var \Illuminate\Foundation\Testing\TestResponse
     */
    protected $testResponse;

    /**
     * @var bool
     */
    protected $debug_mode = false;

    /**
     * @var
     */
    private $client;

    /**
     *
     */
    public function setUp()
    {
        parent::setUp();

        $config = $this->getGuzzleHttpDefaultConfig();
        $this->client = new Client($config);
    }

    protected function getGuzzleHttpDefaultConfig()
    {
        return [
            //'proxy' => [
            //    'http'  => 'tcp://127.0.0.1:1080',
            //    'https' => 'tcp://127.0.0.1:1080'
            //]
        ];
    }

    /**
     * @return mixed
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * @return mixed
     */
    public function getGuzzleResponse()
    {
        return $this->guzzleResponse;
    }

    public function setGuzzleResponse(ResponseInterface $response)
    {
        $this->guzzleResponse = $response;

        return $this->setResponse(new \Illuminate\Http\Response($response->getBody(), $response->getStatusCode(),
            $response->getHeaders()));
    }

    /**
     * @return \Illuminate\Foundation\Testing\TestResponse
     */
    public function getTestResponse()
    {
        return $this->testResponse;
    }

    /**
     * @param string $uri
     * @param array  $query
     * @param array  $options
     *
     * @return $this
     */
    public function get($uri, array $query = [], array $options = [])
    {
        $options = $this->configureOptions($options);
        $this->printArray($options);
        $response = $this->client->get($uri, array_merge($options, [
            'query' => $query,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @param $options
     *
     * @return array
     */
    private function configureOptions($options)
    {
        $default = [
            //'proxy' => [
            //    'http'  => 'tcp://127.0.0.1:1080',
            //    'https' => 'tcp://127.0.0.1:1080'
            //]
        ];

        return array_merge($default, $options);
    }

    /**
     * @param $array
     */
    protected function printArray($array)
    {
        if ($this->debug_mode) {
            print_r($array);
        }
    }

    /**
     * @param string $uri
     * @param array  $data
     * @param array  $options
     *
     * @return $this
     */
    public function post($uri, array $data = [], array $options = [])
    {
        $options = $this->configureOptions($options);
        $response = $this->client->post($uri, array_merge($options, [
            'form_params' => $data,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @param       $uri
     * @param array $multipart
     * @param array $options
     *
     * @return $this
     */
    public function postMultipart($uri, array $multipart = [], array $options = [])
    {
        $options = $this->configureOptions($options);
        $response = $this->client->post($uri, array_merge($options, [
            'multipart' => $multipart,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @param       $uri
     * @param array $data
     * @param array $options
     *
     * @return $this
     */
    public function postMultipartSimple($uri, array $data = [], array $options = [])
    {
        $options = $this->configureOptions($options);
        $multipart = [];
        foreach ($data as $key => $value) {
            $multipart[] = [
                'name'     => $key,
                'contents' => $value,
            ];
        }
        $response = $this->client->post($uri, array_merge($options, [
            'multipart' => $multipart,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @param       $uri
     * @param array $data
     * @param array $options
     *
     * @return $this
     */
    public function head($uri, array $data = [], array $options = [])
    {
        $response = $this->client->head($uri, array_merge($options, [
            'body' => $data,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @param string $uri
     * @param array  $data
     * @param array  $options
     *
     * @return $this
     */
    public function put($uri, array $data = [], array $options = [])
    {
        $options = $this->configureOptions($options);
        $response = $this->client->put($uri, array_merge($options, [
            'form_params' => $data,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @param string $uri
     * @param array  $data
     * @param array  $options
     *
     * @return $this
     */
    public function patch($uri, array $data = [], array $options = [])
    {
        $options = $this->configureOptions($options);
        $response = $this->client->patch($uri, array_merge($options, [
            'form_params' => $data,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @param string $uri
     * @param array  $data
     * @param array  $options
     *
     * @return $this
     */
    public function delete($uri, array $data = [], array $options = [])
    {
        $options = $this->configureOptions($options);
        $response = $this->client->delete($uri, array_merge($options, [
            'form_params' => $data,
        ]));

        return $this->setGuzzleResponse($response);
    }

    /**
     * @return $this
     */
    public function printResponseData()
    {
        print_r($this->getResponseData());

        return $this;
    }

    /**
     * @return mixed
     */
    public function getResponseData()
    {
        return $this->getResponseAsJson();
    }

    /**
     * @param bool $assoc
     *
     * @return mixed
     */
    public function getResponseAsJson($assoc = true)
    {
        return json_decode($this->getResponse()->getContent(), $assoc);
    }

    /**
     * @return mixed
     */
    public function getResponse()
    {
        return $this->response;
    }

    public function setResponse(\Illuminate\Http\Response $response)
    {
        $this->response = $response;
        $this->testResponse = $this->createTestResponse($response);
        $statusCode = $this->response->getStatusCode();
        if ($statusCode >= 300) {
            echo "\nResponse STATUS CODE is $statusCode:\n";
            $responseData = $this->getResponseData();
            if ($responseData) {
                print_r($responseData);
            } else {
                echo $this->getResponse();
            }
        }

        return $this->testResponse;
    }

    /**
     * @return $this
     */
    public function printResponseOriginContent()
    {
        print_r((string) $this->response->getOriginalContent());

        return $this;
    }

    /**
     * @return bool
     */
    public function isResponseSuccess()
    {
        return $this->getResponse()->getStatusCode() < 300;
    }

    /**
     * @return mixed
     */
    public function assertResponseOk()
    {
        $response = $this->getTestResponse();
        return $response->assertStatus(self::HTTP_OK);
    }

    /**
     * @return mixed
     */
    public function assertResponseNoContent()
    {
        $response = $this->getTestResponse();
        return $response->assertStatus(self::HTTP_NO_CONTENT);
    }

    /**
     * @return mixed
     */
    public function assertResponseCreated()
    {
        $response = $this->getTestResponse();
        return $response->assertStatus(self::HTTP_CREATED);
    }

    /**
     * @return mixed
     */
    public function assertResponseUnprocessableEntity()
    {
        $response = $this->getTestResponse();
        return $response->assertStatus(self::HTTP_UNPROCESSABLE_ENTITY);
    }

    /**
     * @return mixed
     */
    public function assertResponseUnauthorized()
    {
        $response = $this->getTestResponse();
        return $response->assertStatus(self::HTTP_UNAUTHORIZED);
    }

    /**
     * @return mixed
     */
    public function assertResponseAccessDenied()
    {
        $response = $this->getTestResponse();
        return $response->assertStatus(self::HTTP_FORBIDDEN);
    }

    /**
     * @param $string
     */
    protected function printLine($string)
    {
        if ($this->debug_mode) {
            echo $string."\n";
        }
    }

    public function assertArrayStructure(array $structure = null, $array = null)
    {

        foreach ($structure as $key => $value) {
            if (is_array($value) && $key === '*') {
                PHPUnit::assertInternalType('array', $array);

                foreach ($array as $arrayItem) {
                    $this->assertArrayStructure($structure['*'], $arrayItem);
                }
            } elseif (is_array($value)) {
                PHPUnit::assertArrayHasKey($key, $array);

                $this->assertArrayStructure($structure[$key], $array[$key]);
            } else {
                PHPUnit::assertArrayHasKey($value, $array);
            }
        }

        return $this;
    }
}